# -*- coding: utf-8 -*-
import time
from functools import partial
from functools import wraps

from django.conf import settings
from django.core.cache import cache
from future.utils import raise_with_traceback
from pymongo.errors import PyMongoError
from redis import RedisError

import track_logging
from common import orm, slave
from common.utils import JsonResponse
from common.utils.exceptions import (Error, DbError,
                                     CacheError)
from common.utils.respcode import StatusCodeDict

_LOGGER = track_logging.getLogger(__name__)


def _wrap2json(data):
    if data is None:
        return JsonResponse(dict(status=0, msg='', data={}), status=200)
    elif isinstance(data, dict) or isinstance(data, list):
        return JsonResponse(dict(status=0, msg='', data=data), status=200)
    else:
        return data


def response_wrapper(func):
    def _wrapper(request, *args, **kwargs):

        if settings.DEBUG:
            return _wrap2json(func(request, *args, **kwargs))

        try:
            return _wrap2json(func(request, *args, **kwargs))
        except Error as e:
            _LOGGER.info('server error! %s %s %s', e.HTTPCODE, unicode(e) or StatusCodeDict.get(e.STATUS),
                         request.path)
            return JsonResponse(
                dict(status=e.STATUS,
                     msg=unicode(e) or StatusCodeDict.get(e.STATUS)),
                status=e.HTTPCODE)
        except:
            user_repr = "%s (%s)" % (request.user.id, request.user.user_name) if request.user else ''
            _LOGGER.exception('REQUEST URL: %s, USER: %s' % (request.path, user_repr))
            return JsonResponse(
                dict(status=Error.STATUS, msg=u'未知错误'),
                status=Error.HTTPCODE)

    return _wrapper


def common_sql_wrapper(orm):
    def handler(func):
        def _wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except:
                orm.session.rollback()
                raise
            finally:
                orm.session.close()

        return _wrapper

    return handler


sql_wrapper = partial(common_sql_wrapper(orm=orm))
slave_wrapper = partial(common_sql_wrapper(orm=slave))


def cache_wrapper(func):
    def _wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except RedisError as e:
            raise_with_traceback(CacheError(e))
        except Error:
            raise
        except Exception as e:
            raise_with_traceback(Error(e))

    return _wrapper


def mongo_wrapper(func):
    def _wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except PyMongoError as e:
            raise_with_traceback(DbError(e))
        except Error:
            raise
        except Exception as e:
            raise_with_traceback(Error(e))

    return _wrapper


def local_memory_cache(key, expr):
    def outer(func):
        def inner():
            value = cache.get(key)
            if value:
                return value
            else:
                value = func()
                cache.set(key, value, expr)
                return value

        return inner

    return outer


def retry(ExceptionToCheck, tries=3, delay=1, backoff=1, logger=None):
    """Retry calling the decorated function using an exponential backoff.

    http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
    original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry

    :param ExceptionToCheck: the exception to check. may be a tuple of
        exceptions to check
    :type ExceptionToCheck: Exception or tuple
    :param tries: number of times to try (not retry) before giving up
    :type tries: int
    :param delay: initial delay between retries in seconds
    :type delay: int
    :param backoff: backoff multiplier e.g. value of 2 will double the delay
        each retry
    :type backoff: int
    :param logger: logger to use. If None, print
    :type logger: logging.Logger instance
    """

    def deco_retry(f):
        @wraps(f)
        def f_retry(*args, **kwargs):
            mtries, mdelay = tries, delay
            while mtries > 1:
                try:
                    return f(*args, **kwargs)
                except ExceptionToCheck, e:
                    msg = "%s, Retrying in %d seconds..." % (str(e), mdelay)
                    if logger:
                        logger.warning(msg)
                    else:
                        print msg
                    if mdelay > 0:
                        time.sleep(mdelay)
                    mtries -= 1
                    mdelay *= backoff
            return f(*args, **kwargs)

        return f_retry  # true decorator

    return deco_retry
